import java.io.*;
import java.util.*;
import common.*;

public class Process extends Thread
{
	private MessagePasser postman;
	private Interface shell;
	private ArrayList<Resource> local_files;
	
	// for ME_TOKENLESS
	protected CriticalSectionRecord cs_in_use = null;		// records of the CS that this process is granted to use
														// // * note: this can be a multiple.
														//     It is possible for a process to receive
														//     multiple permissions (= multiple CS) at a time,
														//     but, it still only be in a CS at a moment.

	// * note: these can be a multiple.
	private ArrayList<CriticalSectionRecord> cs_my_requests_granted;	// records of the CS that this process is granted to use
	protected ArrayList<CriticalSectionRecord> cs_my_requests;			// records of the current requests of this process.
	private ArrayList<CriticalSectionRecord> cs_others_requests;	// records of requests of other process.
	
	
		// * note: i'm allowing redundancy for readability
	// for ME_TOKEN
	protected Integer holder = Def.NOT_ASSIGNED;
	//private TokenRay token = null;
	protected ArrayList<CriticalSectionRecord> cs_requests;
	
	// process state var
	protected boolean running = true;
	protected boolean captain = false;
	protected int myID, sleepTime;
	//private Random rand;
	private boolean cs_run_msg_flag = false;
	protected boolean garbled = false;
	
	private long tokenlost_reported_time_ms = Def.NOT_ASSIGNED;
	private long last_token_ret_reported_time_ms = Def.NOT_ASSIGNED;
	private int last_token_ret_reporter = Def.NOT_ASSIGNED;
	private long last_request_time_ms = Def.NOT_ASSIGNED;
	private int last_request_requester = Def.NOT_ASSIGNED;
	static int done = 0;
	
	// ----------------------------
	// A quick note for the ME implementation:
	//   - The variables for ME are declared 'a few lines above' and instantiated in the 'constructor' @ Process.java
	//   - Interface.java makes records for own requests, right after the msg is sent to everyone 'run()' @ Interface.java
	//   - The message handlers for ME are located in  'public void receive( Message msg )' @ Process.java
	//   - The things to do when RELEASE are in  'public void run()' @ Process.java
	
	public Process(int myID, int sleepTime)
	{
		this.myID = myID;
		this.sleepTime = sleepTime;
		///rand = new Random();
		
		// establishes the network
		try
		{
			postman = new MessagePasser(myID, this);	////new MessagePasser(this, myID);
		}
		catch(IOException e)
		{
			System.out.println("Could not build MessagePasser, quitting");
			System.exit(0);
		}
		
		// resources
		local_files = new ArrayList<Resource>();
		// all proc has 'Conf.N_RESOURCES_PER_NODE' of resources
		System.out.println( "-proc(" + myID + "): generating local resources" );
		for( int i = 0; i < Conf.N_RESOURCES_PER_NODE; i++ ){
			local_files.add( new Resource( "file_" + myID + "_" + i + ".txt", "/", myID ) );
		}
		
		//this.print_filelist();
		
		// interface (console)
		shell = new Interface( this );		// if the preemptive mgmt_console (Monitor.java) is on, don't create the Interface 
		
		// ME related
		if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){	// for TOKENLESS (Ricart-Agrawala)
			cs_my_requests = new ArrayList<CriticalSectionRecord>();
			cs_my_requests_granted = new ArrayList<CriticalSectionRecord>();
			cs_others_requests = new ArrayList<CriticalSectionRecord>();
		
		}else{	// for TOKEN-based (Raymond)
			cs_requests = new ArrayList<CriticalSectionRecord>();
			cs_my_requests = new ArrayList<CriticalSectionRecord>();
			//lock_token_transaction = new Object();
		}
	}//end-of-constructor
	
	
	
	//
	// Thread core
	//
	public void run()
	{
		// autoexec
        if( Conf.MGMT_CONSOLE ){
        	Utils.print_log( "-proc(" + myID + "): monitor's on. Interface shut up." );
            shell.turnoff();       // prevent the screen from getting swamped by prompts
            //shell.start();
        }
        
        // announce my join
        send( Def.JOIN, -1, null, null );
        
        // Execute the main loop of the program
		while( running ){	// running -- for process simulation. !running -- for net test
		
			// only node (physical machine) captain activates its interface 
			if( captain && !shell.status() ){
				shell.turnon();
			}
			
			// token tree check
			synchronized( holder ){
				if( Conf.ME_SCHEME == Def.ME_TOKEN ){
					if( holder == Def.NOT_ASSIGNED ){
						try{
							sleep( 500 );
						}catch( InterruptedException e ){ e.printStackTrace(); }
						
						synchronized( MessagePasser.participants ){
							if( myID == MessagePasser.participants.get( 0 ) ){
								System.out.println( "-proc(" + myID + "): building Raymond's tree" );
								send( Def.TOKENINIT, -1, null, null );
								holder = myID;
							}
						}
					}
				}
			}
			
			
			// running critical section - doing 1 cs per loop
			if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){	// for TOKENLESS (Ricart-Agrawala)
				if( cs_in_use != null ){
					if( cs_run_msg_flag ){
						// DELAY MEASURE
						long delay_ms = cs_in_use.get_grant_time() - cs_in_use.get_request_time();
						if( cs_in_use.get_operation_type() == Def.ME_OPR_READONLY ) Measure.accumulate_read_delay( delay_ms );
						else if( cs_in_use.get_operation_type() == Def.ME_OPR_WRITE ) Measure.accumulate_write_delay( delay_ms );

						
						Utils.print_log( "-proc(" + myID + "): running CS of \"" + cs_in_use.get_location_n_name() + "\"" );
						if( this.is_corrupt( cs_in_use, cs_in_use.crc ) ){
							Utils.print_log( "-proc(" + myID + "): ** this resource is corrupt. (DROP) **" );
							cs_in_use.grant_time_ms =- cs_in_use.duration_ms; 
						}
						cs_run_msg_flag = false;	// make it shut up
					}
					
					// check the time elapsed in the CS
					if( cs_in_use.duration_ms <= System.currentTimeMillis() - cs_in_use.grant_time_ms ){
						Utils.print_log( "-proc(" + myID + "): done with the CS of \"" + cs_in_use.get_location_n_name() + "\"" );
						done++;
						
						// I'm done with the CS, so let's RELEASE it
						
						if( cs_in_use.get_operation_type() == Def.ME_OPR_WRITE )
							send( Def.CSFREE, cs_in_use.owner, cs_in_use.get_location_n_name(), null );
						cs_in_use = null;
						
						// send GRANT for the requests in 'cs_others_requests'
						for( int i = 0; i < cs_others_requests.size(); i++ ){
							// **** TODO: check AGAIN the resource is not in my interest
							CriticalSectionRecord temp = cs_others_requests.remove( i );
							if( temp.get_operation_type() == Def.ME_OPR_WRITE )
								send( Def.GRANT, temp.get_requester(), temp.get_location_n_name(), "w" );
							else
								send( Def.GRANT, temp.get_requester(), temp.get_location_n_name(), "r" );
							//send( Def.GRANT, myID, temp.get_location_n_name(), null );
						}
					}
				}
				// running the next CS
				if( cs_in_use == null && cs_my_requests_granted.size() > 0 ){	// if i'm not using any CS, but have granted
					cs_in_use = cs_my_requests_granted.remove( 0 );
					cs_run_msg_flag = true;
					cs_in_use.grant_time_ms = System.currentTimeMillis();	// check-in with the current UTC
				}
				
				synchronized( cs_my_requests ){
					// timeout for long-waiting 'this.cs_my_requests' 
					for( int i = 0; i < cs_my_requests.size(); i++ ){
						long current_time = System.currentTimeMillis();
						if( current_time - cs_my_requests.get( i ).get_request_time() > Conf.ME_REQUEST_TIMEOUT_MS ){
							cs_my_requests.remove( i );
							break;
						}
					}
				}
				
			}
			
			
			if( Conf.ME_SCHEME == Def.ME_TOKEN ){	// for TOKEN-based (Raymond)

				// Running CS + RELEASE actions + switch the next CS granted
				
				if( cs_in_use != null ){
					if( cs_run_msg_flag ){
						// DELAY MEASURE
						long delay_ms = cs_in_use.get_grant_time() - cs_in_use.get_request_time();
						if( cs_in_use.get_operation_type() == Def.ME_OPR_READONLY ) Measure.accumulate_read_delay( delay_ms );
						else if( cs_in_use.get_operation_type() == Def.ME_OPR_WRITE ) Measure.accumulate_write_delay( delay_ms );
						done++;
						
						Utils.print_log( "-proc(" + myID + "): running CS of \"" + cs_in_use.get_location_n_name() + "\" -- " + cs_in_use.duration_ms );
						if( this.is_corrupt( cs_in_use, cs_in_use.crc ) ){
							Utils.print_log( "-proc(" + myID + "): ** this resource is corrupt. (DROP) **" );
							cs_in_use.grant_time_ms =- cs_in_use.duration_ms; 
						}
						cs_run_msg_flag = false;	// make it shut up
					}
					
					// check the time elapsed in the CS
//					Utils.print_log( "-proc(" + myID + "): d=" + cs_in_use.duration_ms +
//								" c=" + System.currentTimeMillis() + " g=" + cs_in_use.grant_time_ms +
//								" (" + ( System.currentTimeMillis() - cs_in_use.grant_time_ms ) + ")");
					if( cs_in_use.duration_ms <= System.currentTimeMillis() - cs_in_use.grant_time_ms ){
						
						synchronized( cs_requests ){
							if( !cs_requests.isEmpty() ){	// if my Queue is NOT empty,
								CriticalSectionRecord next = cs_requests.remove( 0 );	// delete top( Queue )
								synchronized( holder ){
									holder = next.get_requester();							// send token to the next requester
									send( Def.TOKEN, next.get_requester(), null, null );
								}
								
								if( !cs_requests.isEmpty() ){
									send( Def.REQUEST_TOKEN, holder, null, null );
								}
							}
						}
						
						// I'm done with the CS, so let's RELEASE it
						Utils.print_log( "-proc(" + myID + "): done with the CS of \"" + cs_in_use.get_location_n_name() + "\"" );
						cs_in_use = null;
						
					}
				}
				
				// REQUEST TIME OUT -- file not found
				synchronized( cs_my_requests ){
					long current_time = System.currentTimeMillis();
					for( int i = 0; i < cs_my_requests.size(); i++ ){
						if( !cs_my_requests.isEmpty() && current_time - cs_my_requests.get( i ).get_request_time() > Conf.ME_REQUEST_TIMEOUT_MS ){
							CriticalSectionRecord temp = cs_my_requests.remove( i );
							Utils.print_log( "-proc(" + myID + "): \"" + temp.get_location_n_name() + "\" is not found" );
							break;
						}	
					}
				}
				
				// token guard
				if( postman.getLowestConnected() == myID ){	// i'm the token guardian
					if( this.last_token_ret_reported_time_ms != Def.NOT_ASSIGNED && this.last_request_time_ms != Def.NOT_ASSIGNED
							&& last_request_time_ms - last_token_ret_reported_time_ms > Conf.ME_TOKEN_TIMEOUT_MS ){	// 300s
						System.err.println( "-proc(" + myID + "): alert - token is not being reported" );
					}
				}
				
				
			}//end-of-critical_section_running
			
			
			// sleep -- clock imitation
			try{
				sleep( sleepTime );
			}catch( InterruptedException e ){
				Utils.print_log( "ERROR: critical error (check your code @voa931) -- Process.java" );
			}
		}//end-of-while
	//	System.out.println( "Process: pid=" + myID + " is terminated" );
		
		//// ------------------------- physical network testing routine ------------------------- ////
//		int partnerID = 0;
//		for(int i = 0; running; i++)
//		{
//			try
//			{
//				Thread.sleep(sleepTime);
//				partnerID = rand.nextInt(MessagePasser.getNetworkSize());
//				while(partnerID == myID)
//				{
//					partnerID = rand.nextInt(MessagePasser.getNetworkSize());
//				}
//				////postman.send(partnerID, Integer.toString(i)+"\n");
//				Message msg = new Message( Def.PING, partnerID, myID, System.currentTimeMillis(), "", null );
//				postman.send( msg );
//			}
//			catch(Exception e)
//			{
//				System.out.println("Error sending message: ");
//				e.printStackTrace();
//			}
//		}
//		System.out.println("Node: " + myID + " died");
		//// ------------------------- physical network testing routine ------------------------- ////

	}//end-of-run()
	
	public void receive(String msg)
	{	
		if (msg.equals("close"))
		{
			System.out.println("Partner closed connection.");
			shutdown();
		}
		System.out.println(myID + " Received: " + msg);
	}
	
	/**
	 *  this method invoked by postman.receive(), when postman receives a message and it needs to be handled at the process level.
	 */
	public void receive( Message msg ){
		
		if( Conf.VERBOSE_PROC )
			Utils.print_log( "-proc(" + myID + "): is handling a msg[t=" + msg.get_type() + "] from proc(" + msg.get_sender_id() + ")" );

		// msg, ls
		if( msg.get_type() == Def.MSG ){	// simple msg from a node -> all i need to do is print it out thru the interface
			shell.show_message( msg );
			return;
		}else if( msg.get_type() == Def.LS ){
			ArrayList<String> temp = this.get_filelist();
			String ls = "";
			for( int i = 0; i < temp.size(); i++ ){
				ls = ls + temp.get( i ) + "\n";
			}
			send( Def.ACK, msg.get_sender_id(), ls, null );
			return;
		}else if( msg.get_type() == Def.ACK ){
			// ls return
			if( msg.get_content() != null )
				System.out.print( "\n" + msg.get_content() );
			
			return;
		}else if( msg.get_type() == Def.GARBLE ){
			
			if( garbled ){
				garbled = false;
				Utils.print_log( "-proc(" + myID + "): i'll stop garbling" );
				for( int i = 0; i < local_files.size(); i++ )
					local_files.get( i ).set_crc( ( int )( Math.random() * 99 ) % 64 );	// mimic 64-bit CRC
				
			}else{
				garbled = true;
				Utils.print_log( "-proc(" + myID + "): alright. i'll garble my resources" );
				for( int i = 0; i < local_files.size(); i++ )
					local_files.get( i ).set_crc( Def.JUNK );
			}
			return;
		}else if( msg.get_type() == Def.HOLDER ){
			String holder;
			if( Conf.ME_SCHEME == Def.ME_TOKENLESS )
				holder = "NOT EXIST\n";
			else
				holder = myID + " -> " + this.holder + "\n";
			send( Def.ACK, msg.get_sender_id(), holder, null );
			return;
		}else if( msg.get_type() == Def.QUEUE ){
			Utils.print_log( "" + cs_in_use );
			if( Conf.ME_SCHEME == Def.ME_TOKEN )
				Utils.print_log( "" + cs_requests.size() );
			Utils.print_log( "" + cs_my_requests.size() );
			return;
			
		}else if( msg.get_type() == Def.DROP ){
			if( Conf.ME_SCHEME == Def.ME_TOKEN ){
				// token reset
				if( postman.getLowestConnected() == myID ){
					// token tree rebuilding
					System.out.println( "-proc(" + myID + "): building Raymond's tree" );
					send( Def.TOKENINIT, -1, null, null );
				}
			}
			return;
		}else if( msg.get_type() == Def.ANNOUNCE ){
			Utils.print_log( "-postman(" + myID + "): connection lost" );
			send( Def.DROP, postman.getLowestConnected(), null, null );
			return;
		}

		if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){		// for TOKENLESS (Ricart-Agrawala)
			// msg handler for the tokenless ME scheme
			switch( msg.get_type() ){
				case Def.REQUEST_READ:
					if( this.is_waiting_for( msg.get_content() ) ){
						// if this process is waiting for the requested CS, compare the timestamps
						if( msg.get_timestamp() <= get_my_request_time( msg.get_content() ) ){
							// the incoming request was made earlier: I gotta yield + reply GRANT
							send( Def.GRANT, msg.get_sender_id(), msg.get_content(), "r" );	// if the requested files is local (mine), resource record will be updated in send()
							
						}else{
							// my request was made earlier: I don't reply + enqueue the request
							cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
						}
						
					}else if( cs_in_use == null || !( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) ){
						// if this process doesn't use the requested CS, reply GRANT
						send( Def.GRANT, msg.get_sender_id(), msg.get_content(), "r" );	// if the requested files is local (mine), resource record will be updated in send()
						
					}else if( ( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) && cs_in_use.get_operation_type() == Def.ME_OPR_READONLY ){
						// if this process use the requested CS and its operation is READONLY, reply GRANT
						send( Def.GRANT, msg.get_sender_id(), msg.get_content(), "r" );	// if the requested files is local (mine), resource record will be updated in send()
						
					}else if( ( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) && cs_in_use.get_operation_type() == Def.ME_OPR_WRITE ){
						// if this process use the requested CS and its operation is WRITE, don't reply + enqueue the request
						cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
					}
					break;
					
				case Def.REQUEST_WRITE:
					if( this.is_waiting_for( msg.get_content() ) ){
						// if this process is waiting for the requested CS, compare the timestamps
						if( msg.get_timestamp() <= get_my_request_time( msg.get_content() ) ){
							// the incoming request was made earlier: I gotta yield + reply GRANT
							send( Def.GRANT, msg.get_sender_id(), msg.get_content(), "w" );	// if the requested files is local (mine), resource record will be updated in send()
							
						}else{
							// my request was made earlier: I don't reply + enqueue the request
							cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
						}
						
					}else if( cs_in_use == null || !( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) ){
						// if this process doesn't use the requested CS, reply GRANT
						send( Def.GRANT, msg.get_sender_id(), msg.get_content(), "w" );	// if the requested files is local (mine), resource record will be updated in send()
						
					}else if( ( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) ){
						// if this process use the requested CS (whatever the operation is), don't reply + enqueue the request
						cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
					}
					break;
					
				case Def.GRANT:
					synchronized( cs_my_requests ){
						for( int i = 0; i < cs_my_requests.size(); i++ ){
							if( msg.get_content().equals( cs_my_requests.get( i ).get_location_n_name() ) ){
								// if the GRANT msg came with the resource header --> the sender is the file owner.
								if( msg.get_attachment() != null && msg.get_attachment() instanceof Resource ){
									cs_my_requests.get( i ).owner = msg.get_sender_id();
									cs_my_requests.get( i ).crc = ( ( Resource )msg.get_attachment() ).get_crc();
									
									if( cs_my_requests.get( i ).get_operation_type() == Def.ME_OPR_READONLY ){
										cs_my_requests.get( i ).duration_ms = ( ( Resource )msg.get_attachment() ).get_readtime_ms();
									
									}else if( cs_my_requests.get( i ).get_operation_type() == Def.ME_OPR_WRITE ){
										cs_my_requests.get( i ).duration_ms = ( ( Resource )msg.get_attachment() ).get_writetime_ms();
									}
								}
								
								// mark the sender's field as the GRANT, on my_request list
								//if( cs_my_requests.get( i ).granted_by.indexOf( new Integer( msg.get_sender_id() ) ) == -1 )
								//	Utils.print_log( "cs_grant_check = ok" );
								cs_my_requests.get( i ).granted_by.add( msg.get_sender_id() );
								Utils.print_log( "-proc(" + myID + "): " + "received a GRANT for \"" + msg.get_content() + "\" from proc(" + msg.get_sender_id() + ")" );
								
								// if the request got GRANTed by everyone --> I can run the CS: move the CS to 'cs_my_requests_granted' 
								if( !cs_my_requests.isEmpty() && postman.is_this_everyone( cs_my_requests.get( i ).granted_by ) ){
									
									if( !cs_my_requests.isEmpty() ){
										CriticalSectionRecord temp = cs_my_requests.remove( i );
										if( temp.owner != Def.NOT_ASSIGNED ){
											cs_my_requests_granted.add( temp );
											Utils.print_log( "-proc(" + myID + "): " + "got all GRANTs for \"" + msg.get_content() + "\"" );
										}else{
											Utils.print_log( "-proc(" + myID + "): \"" + msg.get_content() + "\" is not found" );
											
										}
									}
									break;	// break for( int i = 0; i < cs_my_requests.size(); i++ )
								}
							}
						}//end-of-for
					}
					break;

				case Def.CSFREE:
					for( int i = 0; i < local_files.size(); i++ ){
						if( msg.get_content().equals( local_files.get( i ).get_location_n_name() ) ){
							local_files.get( i ).set_granted_for( Def.NOT_ASSIGNED );
							break;	// break 'for'
						}
					}
					break;
					
				default:
					Utils.print_log( "ERROR: " + msg.get_type() + " is unknown message type (check your code @qfv032) -- Process.java" );
			}//end-of-switch
		}else{		// for TOKEN-based (Raymond)
			
//			while( cs_in_use != null ){
//				try {
//					sleep( 2000 );
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
//			}
			// msg handler for the token-based ME scheme
			switch( msg.get_type() ){
				case Def.TOKENINIT:
					synchronized( holder ){
						// build the Raymond's token tree
						if( myID == MessagePasser.participants.get( 0 ) ){
							holder = myID;
							System.out.println( "-proc(" + myID + "): Assigned to be the initial token holder" );
						}else{
							int my_order = MessagePasser.participants.indexOf( myID );
							if( my_order == -1 ){
								Utils.print_log( "ERROR: critical error (check your code @qcda03) -- Process.java" );					
							}
							int temp = ( int )( ( my_order + 1 ) / 2 ) - 1;
							holder = MessagePasser.participants.get( temp );
						}
					}
					System.out.println( "-proc(" + myID + "): my holder = " + holder );
					
					// reset job queue
					this.cs_my_requests.clear();
					this.cs_requests.clear();

					break;
			
					
				case Def.REQUEST_ACK:
					
					String ack_name = msg.get_content();
					Resource ack_file_record = null;
					if( msg.get_attachment() instanceof Resource ){
						ack_file_record = ( Resource )msg.get_attachment(); 
					}else{
						Utils.print_log( "ERROR: is unknown object type (check your code @fev406) -- Process.java" );
					}
					
					// my request record update
					CriticalSectionRecord temp = null;
					
					synchronized( cs_my_requests ){
						for( int i = 0; i < cs_my_requests.size(); i++ ){
							if( ack_name.equals( cs_my_requests.get( i ).get_location_n_name() ) ){
								
								temp = cs_my_requests.remove( i );
								temp.owner = msg.get_sender_id();
								break;
							}
						}
					}
					
					if( temp != null ){
						if( ack_file_record != null ){
							temp.crc = ack_file_record.get_crc();
							if( temp.get_operation_type() == Def.ME_OPR_READONLY ) temp.duration_ms = ack_file_record.get_readtime_ms();
							else if( temp.get_operation_type() == Def.ME_OPR_WRITE ) temp.duration_ms = ack_file_record.get_writetime_ms();
							else Utils.print_log( "ERROR: is unknown object type (check your code @gp2041) -- Process.java" ); 
						}else{
							Utils.print_log( "ERROR: is unknown object type (check your code @ggo42r) -- Process.java" );
						}
					}
					// -- CriticalSectionRecord temp is now have complete info --

					synchronized( cs_requests ){
//						if( holder == myID && cs_requests.isEmpty() ){
//							Utils.print_log( "-proc(" + myID + "): i'm the token holder" );
//							cs_in_use = temp;
//							cs_in_use.grant_time_ms = System.currentTimeMillis();
//							cs_run_msg_flag = true;
//							
//						}else{
						{
							if( holder != myID && cs_requests.isEmpty() ){
								send( Def.REQUEST_TOKEN, holder, null, null );
								Utils.print_log( "-proc(" + myID + "): REQUEST_TOKEN is sent to proc(" + holder + ")" );
							}
							cs_requests.add( temp );
							Utils.print_log( "-proc(" + myID + "): the REQUEST is queued" );
						}
					}
					break;
					
				// these are not the request, only tells the file location
				case Def.REQUEST_READ:
				case Def.REQUEST_WRITE:
					if( postman.getLowestConnected() == myID ){
						this.last_request_time_ms = System.currentTimeMillis();
						this.last_request_requester = msg.get_sender_id();
					}
					// see if the file is mine
					String req_name = msg.get_content();
					
					for( int i = 0; i < local_files.size(); i++ ){
						// see if the request is for one of my local files
						if( req_name.equals( local_files.get( i ).get_location_n_name() ) ){	// if it's my local file
							Utils.print_log( "-proc(" + myID + "): received a request for a local file" );
							Resource record = local_files.get( i );
							send( Def.REQUEST_ACK, msg.get_sender_id(), msg.get_content(), record );
							break;
						}
					}
					break;
										
				case Def.REQUEST_TOKEN:
					Utils.print_log( "-proc(" + myID + "): REQUEST_TOKEN is received from proc(" + msg.get_sender_id() + ")"  );
					
					synchronized( holder ){
						if( holder == myID ){	// if I'm holding the token
							
							if( cs_in_use != null ){
								Utils.print_log( "-proc(" + myID + "): IN USE - enqueue" );
								synchronized( cs_requests ){
									cs_requests.add( new CriticalSectionRecord( msg ) );
								}
								
							}else{
								Utils.print_log( "-proc(" + myID + "): NOT IN USE - toss the token" );
								
								holder = msg.get_sender_id();	//     set my "holder" to (sender)
								send( Def.TOKEN, msg.get_sender_id(), null, null );	//     send [token] to (sender)
							
								Utils.print_log( "-proc(" + myID + "): TOKEN is sent to proc(" + msg.get_sender_id() + ")"  );
								
								synchronized( cs_requests ){
									if( !cs_requests.isEmpty() ){
										send( Def.REQUEST_TOKEN, holder, null, null );
									}
								}
							}
							
						
						}else{	// if I'm not holding token ... "holder is NOT me"
							synchronized( cs_requests ){
								boolean pending = !cs_requests.isEmpty();
								cs_requests.add( new CriticalSectionRecord( msg ) );
								
								 
								if( !pending ){
									send( Def.REQUEST_TOKEN, holder, null, null );
									Utils.print_log( "-proc(" + myID + "): REQUEST_TOKEN is relayed to proc(" + holder + ")" );
								}
							}	
						}
					}
					break;
					
				case Def.TOKEN:
					Utils.print_log( "-proc(" + myID + "): TOKEN is received from proc(" + msg.get_sender_id() + ")"  );
					
					// token reporting (for FT)
					synchronized( holder ){
						holder = myID;
						send( Def.TOKENRET, postman.getLowestConnected(), null, null );	// sending a beacon msg to COOR
						
						// handling a request in my queue
						CriticalSectionRecord next = null;
						synchronized( cs_requests ){
							if( !cs_requests.isEmpty() ){
								next = cs_requests.remove( 0 );		// k = delete top entry of (id) from my queue
							}
						}
						
						if( next != null ){
							if( next.get_requester() == myID ){
								cs_in_use = next;
								cs_in_use.grant_time_ms = System.currentTimeMillis();
								cs_run_msg_flag = true;
							}else{
								holder = next.get_requester();
								send( Def.TOKEN, next.get_requester(), null, null );
								
								synchronized( cs_requests ){
									if( !cs_requests.isEmpty() ){
										send( Def.REQUEST_TOKEN, holder, null, null );
									}
								}
							}
						}
					}
					break;
					
				case Def.TOKENRET:
					if( postman.getLowestConnected() == myID ){
						last_token_ret_reported_time_ms = System.currentTimeMillis();
						last_token_ret_reporter = msg.get_sender_id();
						//Utils.print_log( "rep" );
					}else{
						Utils.print_log( "ERROR: " + msg.get_type() + " is unexpected message for me (check your code @dpq103) -- Process.java" );
					}
					break;
					
				default:
					Utils.print_log( "ERROR: " + msg.get_type() + " is unknown message type (check your code @vhq535) -- Process.java" );
			}//end-of-switch
		}//end-of-if~elseif
	}//end-of-receive( Message msg )
	
	public void send( int msg_type, int rcv_id, String str, Object attach ){
		if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){
			// if the msg_type is GRANT and the original request is for one of my resource,
			// I'll reply GRANT with the resource information
			if( msg_type == Def.GRANT ){
				for( int i = 0; i < local_files.size(); i++ ){
					// see if the request is for one of my local files
					if( str.equals( local_files.get( i ).get_location_n_name() ) ){	// if it's my local file
						int opr_type = Def.ME_OPR_READONLY;
						// check opr type
						if( attach instanceof String ){
							String temp = ( String )attach;
							if( temp.equals( "w" ) ){
								System.out.println( "-proc(" + myID + "): gave GRANT_wr for my local resource to proc(" + rcv_id + ")" );
								attach = null;
								opr_type = Def.ME_OPR_WRITE;
							}else if( temp.equals( "r" ) ){
								System.out.println( "-proc(" + myID + "): gave GRANT_rd for my local resource to proc(" + rcv_id + ")" );
								attach = null;
							}
						}

						// attach the file header to the outgoing msg
						attach = local_files.get( i );
						if( local_files.get( i ).get_granted_for() == Def.ME_OPR_WRITE ){
							//Utils.print_log( "ERROR: you gotta think about here (check your code @dpf394) -- Process.java" );
						}
						local_files.get( i ).set_granted_for( opr_type );	// WRITE OR READ
						//Utils.print_log( "" + opr_type );
						
						break;
					}
				}
			}
			
			// create + send a message
			Message msg = new Message( msg_type, rcv_id, myID, System.currentTimeMillis(), str, attach );
			postman.send( msg );
			
		}else{ 	// for TOKEN-based (Raymond)
			Message msg = new Message( msg_type, rcv_id, myID, System.currentTimeMillis(), str, attach );
				// Message( int _type, int _rcvID, int _sdrID, long _timestamp, String _content, Object _attach )
			postman.send( msg );
		}
	}
	
	public void addPartner(int partnerID)
	{
		if(postman.addConnection(partnerID))
		{
			//if( Conf.VERBOSE_PROC )
				System.out.println("Successfully connected to partner " + partnerID);
		}
		else
		{
			//if( Conf.VERBOSE_PROC )
				System.out.println("Could not connect to partner " + partnerID);
		}
	}
	
	public void shutdown()
	{
		shell.turnoff();
		
		try
		{
			postman.close();
		}
		catch (Exception e)
		{
			System.out.println("Problem closing the MessagePasser");
		}
		running = false;
	}
	
	public MessagePasser get_postman(){ return postman; }
	public Interface get_shell(){ return shell; }
	public ArrayList<Resource> get_files(){ return local_files; }
	
	private boolean is_corrupt( CriticalSectionRecord csr, int crc ){
		if( crc != csr.crc )
			return false;
		if( crc != Def.NOT_ASSIGNED )
			return false;
		
		return true;
	}
	
	// for ME TOKENLESS
	public boolean is_waiting_for( String location_n_name ){
		synchronized( cs_my_requests ){
			for( int i = 0; i < cs_my_requests.size(); i++ ){
				if( location_n_name.equals( cs_my_requests.get( i ).get_location_n_name() ) )
					return true;
			}
			return false;
		}
	}
	
	public long get_my_request_time( String location_n_name ){
		synchronized( cs_my_requests ){
			for( int i = 0; i < cs_my_requests.size(); i++ ){
				if( location_n_name.equals( cs_my_requests.get( i ).get_location_n_name() ) )
					return cs_my_requests.get( i ).get_request_time();
			}
			
			Utils.print_log( "ERROR: critical error (check your code @dfo348) -- Process.java" );
			return Def.ME_NULL;	// shouldn't be happening
		}
	}
	
	public ArrayList<String> get_filelist(){
		ArrayList<String> list = new ArrayList<String>();
		
		for( int i = 0; i < local_files.size(); i++ )
			list.add( local_files.get( i ).get_location_n_name() );
		
		return list;
	}
	
	public void print_filelist(){
		ArrayList<String> list = get_filelist();
		for( int i = 0; i < list.size(); i++ )
			Utils.print_log( list.get(i) );
	}
}
